//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

package ants.p2p.filesharing;

import java.io.*;
import java.beans.*;

import ants.p2p.messages.*;

import org.apache.log4j.*;
import kerjodando.casper.util.ConstantUtil;

public class PartialFile {

	static Logger _logger = Logger.getLogger(PartialFile.class.getName());

	File f = null;

	File downloadInfos = null;

	String name;

	String source;

	long fileSize;

	byte[] hash;

	ByteWrapper[] fileParts;

	FilePushMessage fpm;

	public long lastId = 0;

	int groupFactor;

	long lastActivityTime = System.currentTimeMillis();

	PropertyChangeSupport pcs;

	public static int computeGroupFactor(int blockSize){
		return 1;
		//upgrade to 1.6.0
		//return blockSize >= ConstantUtil.MBYTES ? 1 : 5;
		/* by casper to upgrade to kerjo 1.5.9
		 * if (blockSize >= ConstantUtil.MBYTES) return 1; else //if (blockSize <
		 * ConstantUtil.KBYTES_64) return 5; else { double logarithm =
		 * (Math.log(blockSize) / Math.log(2)); if (logarithm -
		 * Math.floor(logarithm) == 0) { blockSize = blockSize /
		 * ConstantUtil.KBYTES_64; blockSize = 10 - (int)
		 * Math.floor((Math.log(blockSize) / Math .log(2)) * 2); return
		 * blockSize; } else { throw new Exception("BlockSize is not a power of
		 * 2: " + blockSize); } }
		 */
	}

	public long getLastId() {
		return this.lastId;
	}

	public String getName() {
		return this.name;
	}

	public byte[] getHash() {
		return this.hash;
	}

	public long getLastActivityTime() {
		return this.lastActivityTime;
	}

	public PartialFile(String name, String source, byte[] hash, boolean resume,
			long offset, FilePushMessage fpm, PropertyChangeSupport pcs)
			throws Exception {
		name = WarriorAnt.chunksHome + name;
		downloadInfos = new File(name + ".tmp");
		if (downloadInfos.exists() && resume == false) {
			ObjectInputStream fis = new ObjectInputStream(new FileInputStream(
					downloadInfos));
			String oldName = (String) fis.readObject();
			fis.close();
			f = new File(oldName);
			if (f.exists())
				f.delete();
			f = new File(name);
			if (f.exists())
				f.delete();
			ObjectOutputStream fos = new ObjectOutputStream(
					new FileOutputStream(downloadInfos, false));
			fos.writeObject(name);
			fos.close();
		} else if (!downloadInfos.exists() && resume == true) {
			throw new Exception("Resuming error");
		} else if (downloadInfos.exists() && resume == true) {
			ObjectInputStream fis = new ObjectInputStream(new FileInputStream(
					downloadInfos));
			String oldName = (String) fis.readObject();
			fis.close();
			f = new File(oldName);
			if (f.length() != offset)
				throw new Exception("Resuming error");
		} else {
			f = new File(name);
			if (f.exists())
				f.delete();
			ObjectOutputStream fos = new ObjectOutputStream(
					new FileOutputStream(downloadInfos, false));
			fos.writeObject(name);
			fos.close();
		}
		this.pcs = pcs;
		this.fpm = fpm;
		this.name = name;
		this.source = source;
		this.hash = hash;
		this.fileSize = fpm.getFileSize().longValue();
		this.groupFactor = computeGroupFactor(fpm.getBlockSize().intValue());
		fileParts = new ByteWrapper[groupFactor];
	}

	public FilePushMessage getFilePushMessage() {
		return this.fpm;
	}

	public synchronized void appendBytes(byte[] b, long id) {
		try {
			if (id < (lastId * groupFactor)) {
				return;
			}
			if (fileParts[(int) (id - (lastId * groupFactor))] != null) {
				return;
			}
			_logger.debug("Writing to memory part " + id);
			this.pcs.firePropertyChange("bytesGroupAppendedToFilePart", null,
					this);
			fileParts[(int) (id - (lastId * groupFactor))] = new ByteWrapper(b);
			if (receivedBlock()) {
				finalizeFile();
				this.pcs.firePropertyChange("byteBlockFinalizedInFilePart",
						null, this);
				lastId++;
			}
			this.lastActivityTime = System.currentTimeMillis();
		} catch (Exception e) {
			_logger.error("", e);
		}
	}

	public boolean receivedBlock() {
		for (int x = 0; x < fileParts.length; x++) {
			if (fileParts[x] == null)
				return false;
		}
		return true;
	}

	public synchronized void finalizeFile() {
		try {
			_logger.debug("Finalizing");
			FileOutputStream fos = new FileOutputStream(f, true);
			for (int x = 0; x < fileParts.length; x++) {
				if (fileParts[x] != null) {
					fos.write(((ByteWrapper) fileParts[x]).content);
				} else
					break;
			}
			for (int x = 0; x < fileParts.length; x++) {
				fileParts[x] = null;
			}
			fos.close();
			this.downloadInfos.delete();
			System.gc();
		} catch (Exception e) {
			_logger.error("", e);
		}
	}

	public boolean equals(Object o) {
		if (o instanceof PartialFile)
			return compareHash(((PartialFile) o).hash, this.hash)
					&& ((PartialFile) o).getFilePushMessage().equals(
							this.getFilePushMessage());
		else
			return o == this;
	}

	public long getReceivedSize() {
		return f.length();
	}

	public static boolean compareHash(byte[] h1, byte[] h2) {
		if (h1.length != h2.length)
			return false;
		for (int x = 0; x < h1.length; x++) {
			if (h1[x] != h2[x])
				return false;
		}
		return true;
	}
}

class ByteWrapper {
	byte[] content;

	public ByteWrapper(byte[] content) {
		this.content = content;
	}
}
